package com.duang.framework.dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import com.duang.framework.config.SystemConfiguration;
import com.duang.framework.dao.Model.Property;

abstract public class BaseDaoTemplate<T> implements BaseDao<T> {
	protected DataSource dataSource;
	protected Model model;
	protected String saveSql;
	protected String deleteSql;
	protected String getByIdSql;
	protected String updateSql;
	protected Dialect dialect;
	private final ThreadLocal<DB> dbLocal = new ThreadLocal<DB>();

	public BaseDaoTemplate(DataSource dataSource, Model model, Dialect dialect) {
		init(dataSource, model, dialect);
	}

	void init(Model model) {
		System.out.println(SystemConfiguration.getAppConfig()
				.getDataSourceProvider().getDataSource());
		init(SystemConfiguration.getAppConfig().getDataSourceProvider()
				.getDataSource(), model, SystemConfiguration.getAppConfig()
				.getUseDialect());

	}

	void init(DataSource dataSource, Model model, Dialect dialect) {
		this.dataSource = dataSource;
		this.model = model;
		this.dialect = dialect;
		this.saveSql = dialect.save(model);
		this.deleteSql = dialect.delete(model);
		this.getByIdSql = dialect.getById(model);
		this.updateSql = dialect.update(model);

	}

	public BaseDaoTemplate(Model model) {
		init(model);
	}

	public BaseDaoTemplate() {
		Model model = Model.getModel(this.getClass()
				.getAnnotation(ModelSource.class).value());
		init(model);
	}

	public void close() throws DuangDaoException {
		close(getCurrentDB());
	}

	public void close(DB db) throws DuangDaoException {
		db.closeTransaction();
		db.close();
	}

	public void open() {
		DB db = DB.openDB(dataSource);
		dbLocal.set(db);
	}

	public void startTransaction(DB db) throws DuangDaoException {
		db.openTransaction();
	}

	public void startTransaction() throws DuangDaoException {
		open();
		getCurrentDB().openTransaction();
	}

	public void commit() throws DuangDaoException {
		DB db = getCurrentDB();
		db.commit();
		close(db);
	}

	protected void update(String sql, Object[] whereArg, Object... args)
			throws DuangDaoException {
		try {
			log(sql);
			PreparedStatement ps = getDB().getPreparedStatement(sql);
			for (int i = 1; i <= args.length; i++) {
				ps.setObject(i, args[i - 1]);
			}
			for (int j = 1; j < whereArg.length; j++)
				ps.setObject(args.length + j, whereArg[j - 1]);
			ps.executeUpdate();
		} catch (SQLException e) {
			throw new DuangDaoException(e);
		}
	}

	public void update(T object) throws DuangDaoException {
		// do cache
		try {
			DB db = getDB();
			log(updateSql);
			PreparedStatement ps = db.getPreparedStatement(updateSql);
			setPreparedStatementValue(ps, model.getPropertyList(), object);
			ps.setObject(model.getPropertyList().size() + 1, model
					.getPrimaryKey().invokeReadMethod(object));
			ps.executeUpdate();
		} catch (DuangDaoException e1) {
			throw e1;
		} catch (Exception e) {
			throw new DuangDaoException(e);
		}

	}

	PreparedStatement getPs(String sql) {
		return getDB().getPreparedStatement(sql);
	}

	public PreparedStatement getPs(DB db, String sql) {
		return db.getPreparedStatement(sql);
	}

	public void delete(int id) throws DuangDaoException {
		try {
			DB db = getDB();
			log(deleteSql);
			PreparedStatement ps = getPs(db, deleteSql);
			ps.setObject(1, //
					id);
			ps.executeUpdate();
		} catch (SQLException e) {
			throw new DuangDaoException(e);
		} catch (DuangDaoException e1) {
			throw e1;
		}
	}

	/**
	 * @deprecated
	 */
	public void delete(T object) throws DuangDaoException {
		try {
			DB db = getDB();
			log(deleteSql);
			PreparedStatement ps = getPs(db, deleteSql);
			ps.setObject(1, //
					model.getPrimaryKey().invokeReadMethod(object));
			ps.executeUpdate();
		} catch (SQLException e) {
			throw new DuangDaoException(e);
		} catch (DuangDaoException e1) {
			throw e1;
		}
	}

	void setPreparedStatementValue(PreparedStatement ps,
			List<Property> propertyList, Object object) throws SQLException {
		int index = 1;
		for (Property propTemp : model.getPropertyList()) {
			ps.setObject(index++, //
					propTemp.invokeReadMethod(object));

		}

	}

	void log(String sql) {
		if (SystemConfiguration.isDedug())
			System.out.println("excute sql ------>" + sql);
	}

	public void save(T object) throws DuangDaoException {
		try {
			// do cache
			DB db = getDB();
			log(saveSql);
			PreparedStatement ps = db.getPreparedStatement(saveSql);
			setPreparedStatementValue(ps, model.getPropertyList(), object);
			ps.executeUpdate();
		} catch (DuangDaoException e1) {
			throw e1;
		} catch (Exception e) {
			throw new DuangDaoException(e);
		}

	}

	public DB getCurrentDB() throws DuangDaoException {
		DB db;
		if ((db = dbLocal.get()) == null) {
			DB db1 = DB.openDB(dataSource);
			// System.out.println(db1);
			dbLocal.set(db1);
			return db1;
		}
		return db;
	}

	DB getDB() throws DuangDaoException {
		DB db;
		if ((db = dbLocal.get()) == null) {
			throw new DuangDaoException("must open db!!!");
		} else {
			return db;
		}
	}

	protected T getBySql(String sql, Object... whereArgs)
			throws DuangDaoException {
		ResultSet rs = null;
		try {
			DB db = getDB();
			PreparedStatement ps;
			ps = db.getPreparedStatement(sql);
			log(sql);
			for (int i = 1; i <= whereArgs.length; i++) {
				ps.setObject(i, whereArgs[i - 1]);
			}
			rs = ps.executeQuery();
			Object obj = model.getClazz().newInstance();
			if (rs.next()) {
				for (Property property : model.getPropertyList()) {
					property.invokeWriterMethod(obj,
							rs.getObject(property.getColumnName()));
				}
				model.getPrimaryKey().invokeWriterMethod(obj,
						rs.getObject(model.getPrimaryKey().getColumnName()));
			} else {
				return null;
			}
			return (T) obj;
		} catch (DuangDaoException e1) {
			throw e1;
		} catch (Exception e) {
			throw new DuangDaoException(e);
		} finally {
			ExcuteSQLUtils.close(rs);
		}
	}

	public T getById(int id) throws DuangDaoException {
		return getBySql(this.getByIdSql, id);
	}
}
